package com.w08e.waai;

import java.io.*;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * @Author: 梦想成为超人的猪猪侠
 * @Date: 2025/7/11 14:29
 */
public class BinanceApiManager {
    // 配置参数
    private static final String BINANCE_API_URL = "https://api.binance.com/api/v3/klines";
    private static final String DEFAULT_SYMBOL = "ETHUSDT";
    private static final List<String> DEFAULT_INTERVALS = Arrays.asList("1d", "4h", "1h", "30m", "15m", "5m");
    private static final String DEFAULT_START_DATE = "2025-07-10";
    private static final String OUTPUT_DIR = "./binance_data";
    private static final ZoneId TIMEZONE = ZoneId.of("UTC");

    private static final HttpClient httpClient = HttpClient.newHttpClient();
    private static final ObjectMapper objectMapper = new ObjectMapper();

    // K线数据类
    public static class KlineData {
        public long openTime;
        public long closeTime;
        public double open;
        public double high;
        public double low;
        public double close;
        public double volume;
        public double quoteVolume;
        public int trades;
        public double takerBuyBase;
        public double takerBuyQuote;

        public KlineData(JsonNode data) {
            this.openTime = data.get(0).asLong();
            this.open = data.get(1).asDouble();
            this.high = data.get(2).asDouble();
            this.low = data.get(3).asDouble();
            this.close = data.get(4).asDouble();
            this.volume = data.get(5).asDouble();
            this.closeTime = data.get(6).asLong();
            this.quoteVolume = data.get(7).asDouble();
            this.trades = data.get(8).asInt();
            this.takerBuyBase = data.get(9).asDouble();
            this.takerBuyQuote = data.get(10).asDouble();
        }

        public String toCsvRow() {
            return String.format("%s,%s,%.8f,%.8f,%.8f,%.8f,%.8f,%s,%.8f,%d,%.8f,%.8f",
                    formatTimestamp(openTime),
                    formatTimestamp(closeTime),
                    open, high, low, close, volume,
                    formatTimestamp(closeTime),
                    quoteVolume, trades, takerBuyBase, takerBuyQuote);
        }

        private String formatTimestamp(long timestamp) {
            return Instant.ofEpochMilli(timestamp).atZone(TIMEZONE).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
        }
    }

    /**
     * 从Binance API获取历史K线数据
     */
    public static List<KlineData> fetchBinanceKlines(String symbol, String interval,
                                                     LocalDateTime startTime, LocalDateTime endTime) {
        try {
            long startTimestamp = startTime.atZone(TIMEZONE).toInstant().toEpochMilli();
            long endTimestamp = endTime.atZone(TIMEZONE).toInstant().toEpochMilli();

            String url = String.format("%s?symbol=%s&interval=%s&startTime=%d&endTime=%d&limit=1000",
                    BINANCE_API_URL, symbol, interval, startTimestamp, endTimestamp);

            HttpRequest request = HttpRequest.newBuilder()
                    .uri(URI.create(url))
                    .timeout(Duration.ofSeconds(10))
                    .GET()
                    .build();

            HttpResponse<String> response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());

            if (response.statusCode() != 200) {
                System.out.println("🚨 请求失败: HTTP " + response.statusCode());
                return new ArrayList<>();
            }

            JsonNode jsonData = objectMapper.readTree(response.body());

            if (!jsonData.isArray() || jsonData.isEmpty()) {
                System.out.println("⚠️ 无数据返回: " + symbol + " " + interval + " " + startTime + "-" + endTime);
                return new ArrayList<>();
            }

            List<KlineData> klineList = new ArrayList<>();
            for (JsonNode item : jsonData) {
                klineList.add(new KlineData(item));
            }

            return klineList;

        } catch (Exception e) {
            System.out.println("🚨 请求失败: " + e.getMessage());
            return new ArrayList<>();
        }
    }

    /**
     * 获取所有时间粒度的完整历史数据
     */
    public static void fetchAllHistorical(String symbol, List<String> intervals,
                                          String startDate, String endDate) {
        try {
            // 创建输出目录
            Path outputPath = Paths.get(OUTPUT_DIR);
            if (!Files.exists(outputPath)) {
                Files.createDirectories(outputPath);
            }

            LocalDateTime startDateTime = parseDate(startDate);
            LocalDateTime endDateTime = endDate != null ? parseDate(endDate) : LocalDateTime.now(TIMEZONE);

            for (String interval : intervals) {
                String filename = String.format("%s_%s_%s_%s.csv",
                        symbol, interval,
                        startDateTime.format(DateTimeFormatter.ofPattern("yyyyMMdd")),
                        endDateTime.format(DateTimeFormatter.ofPattern("yyyyMMdd")));
                Path filepath = outputPath.resolve(filename);

                if (Files.exists(filepath)) {
                    System.out.println("⏩ 文件已存在: " + filename + ", 跳过...");
                    continue;
                }

                System.out.println("⏬ 下载 " + symbol + " " + interval + " 数据: " + startDateTime + " 至 " + endDateTime + "...");

                List<KlineData> allData = new ArrayList<>();
                LocalDateTime currentDate = startDateTime;

                // 根据时间粒度设置步长
                Duration step = getStepForInterval(interval);

                while (currentDate.isBefore(endDateTime)) {
                    LocalDateTime nextDate = currentDate.plus(step);
                    if (nextDate.isAfter(endDateTime)) {
                        nextDate = endDateTime;
                    }

                    List<KlineData> chunkData = fetchBinanceKlines(symbol, interval, currentDate, nextDate);

                    if (!chunkData.isEmpty()) {
                        allData.addAll(chunkData);
                        System.out.println("  ✅ 获取 " + chunkData.size() + " 条记录: " + currentDate + " → " + nextDate);
                    } else {
                        System.out.println("  ⚠️ 无数据: " + currentDate + " → " + nextDate);
                    }

                    currentDate = nextDate.plusSeconds(1);

                    // 休眠200毫秒避免频率限制
                    Thread.sleep(200);
                }

                if (!allData.isEmpty()) {
                    // 去重并排序
                    allData = removeDuplicatesAndSort(allData);

                    // 保存为CSV
                    saveToCsv(allData, filepath);
                    System.out.println("💾 保存 " + allData.size() + " 条记录至 " + filename);
                } else {
                    System.out.println("❌ 未找到 " + symbol + " " + interval + " 的数据");
                }
            }

        } catch (Exception e) {
            System.out.println("🔥 严重错误: " + e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 根据时间粒度获取步长
     */
    private static Duration getStepForInterval(String interval) {
        switch (interval) {
            case "1m": return Duration.ofDays(1);
            case "5m": return Duration.ofDays(3);
            case "15m": return Duration.ofDays(7);
            case "30m": return Duration.ofDays(14);
            case "1h": return Duration.ofDays(30);
            case "4h": return Duration.ofDays(60);
            case "1d": return Duration.ofDays(365);
            default: return Duration.ofDays(30);
        }
    }

    /**
     * 解析日期字符串
     */
    private static LocalDateTime parseDate(String dateStr) {
        return LocalDate.parse(dateStr, DateTimeFormatter.ofPattern("yyyy-MM-dd"))
                .atStartOfDay(TIMEZONE).toLocalDateTime();
    }

    /**
     * 去重并排序
     */
    private static List<KlineData> removeDuplicatesAndSort(List<KlineData> data) {
        return data.stream()
                .collect(Collectors.toMap(
                        k -> k.openTime,
                        k -> k,
                        (existing, replacement) -> existing
                ))
                .values()
                .stream()
                .sorted(Comparator.comparingLong(k -> k.openTime))
                .collect(Collectors.toList());
    }


    /**
     * 保存数据到CSV文件
     */
    private static void saveToCsv(List<KlineData> data, Path filepath) throws IOException {
        try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(filepath))) {
            // 写入CSV头部
            writer.println("open_time,close_time,open,high,low,close,volume,close_time_formatted,quote_volume,trades,taker_buy_base,taker_buy_quote");

            // 写入数据
            for (KlineData kline : data) {
                writer.println(kline.toCsvRow());
            }
        }
    }

    /**
     * 解析命令行参数
     */
    public static void main(String[] args) {
        try {
            // 解析命令行参数
            String symbol = DEFAULT_SYMBOL;
            List<String> intervals = new ArrayList<>(DEFAULT_INTERVALS);
            String startDate = DEFAULT_START_DATE;
            String endDate = null;

            for (int i = 0; i < args.length; i++) {
                switch (args[i]) {
                    case "--symbol":
                        if (i + 1 < args.length) {
                            symbol = args[++i];
                        }
                        break;
                    case "--intervals":
                        intervals.clear();
                        i++;
                        while (i < args.length && !args[i].startsWith("--")) {
                            intervals.add(args[i++]);
                        }
                        i--; // 回退一位，因为外层循环会增加
                        break;
                    case "--start":
                        if (i + 1 < args.length) {
                            startDate = args[++i];
                        }
                        break;
                    case "--end":
                        if (i + 1 < args.length) {
                            endDate = args[++i];
                        }
                        break;
                    case "--help":
                        printHelp();
                        return;
                }
            }

            // 验证结束日期格式
            if (endDate != null) {
                try {
                    parseDate(endDate);
                } catch (Exception e) {
                    System.out.println("⚠️ 结束日期格式错误, 使用当前日期");
                    endDate = null;
                }
            }

            System.out.println("=".repeat(50));
            System.out.println("🚀 开始下载 " + symbol + " 历史数据");
            System.out.println("📅 时间范围: " + startDate + " 至 " + (endDate != null ? endDate : "现在"));
            System.out.println("⏱ 时间粒度: " + String.join(", ", intervals));
            System.out.println("=".repeat(50));

            fetchAllHistorical(symbol, intervals, startDate, endDate);

            System.out.println("=".repeat(50));
            System.out.println("✅ 数据获取完成!");
            System.out.println("📁 数据保存在: " + Paths.get(OUTPUT_DIR).toAbsolutePath());
            System.out.println("=".repeat(50));

        } catch (Exception e) {
            System.out.println("🔥 程序异常: " + e.getMessage());
            e.printStackTrace();
        }
    }

    /**
     * 打印帮助信息
     */
    private static void printHelp() {
        System.out.println("币安历史K线数据下载工具");
        System.out.println("用法: java BinanceKlineDownloader [选项]");
        System.out.println("选项:");
        System.out.println("  --symbol <符号>       交易对符号 (例如: ETHUSDT)");
        System.out.println("  --intervals <粒度>    K线时间粒度 (例如: 1m 5m 1h 1d)");
        System.out.println("  --start <日期>        开始日期 (YYYY-MM-DD)");
        System.out.println("  --end <日期>          结束日期 (YYYY-MM-DD), 默认当前时间");
        System.out.println("  --help                显示此帮助信息");
    }
}
